Skip to content

fix(driver-sql): 空 $and/$or/$not 按布尔单位元编译,$or: [] 不再返回全表 (#5134) - #5243

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5134-sql-driver-boolean-identity
Aug 4, 2026
Merged

fix(driver-sql): 空 $and/$or/$not 按布尔单位元编译,$or: [] 不再返回全表 (#5134)#5243
os-zhuang merged 2 commits into
mainfrom
claude/issue-5134-sql-driver-boolean-identity

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5134

含 07:29Z 注记并入的 #5146 分叉二($not 空子分组 → 方言 FALSE)。#5146 剩余的 NULL 语义分叉未触碰,非空 $not 的编译方式逐字节未变。

问题

applyFilterCondition 把每个组合子都编译成一个 knex 分组回调,而 knex 对「一个子句都没加进去的分组」不产出任何 SQL。于是**「这个组是空的」和「这个组已被满足」编译成了同一条查询**。丢弃子句不等于套用单位元,而两个单位元的方向是相反的:

写法 布尔代数 修前实测 错的方向
{ $and: [] } TRUE → 全部行 全表 碰巧正确
{ $or: [] } FALSE → 零行 全表 静默放松
{ $or: [{a}, {}] } {} 是 TRUE 析取项 → 全部行 (a = ?) 静默收紧
{ $not: {} } NOT TRUE ≡ FALSE零行 全表 静默放松

$and: [] 恰好正确的理由不是代码理解了单位元,而是「丢掉」在 AND 侧碰巧等价于 TRUE —— 同一段代码在 OR 与 NOT 侧就必然错。

放松的那两格是安全相关的:$or: [] 最常见的来源正是「本该有条件、但循环一个析取项都没填进去」的 RLS read scope,把它当成全表意味着本该看不到任何行的人拿到了整表

同仓另外两个后端(formulamatchesFilterConditiondriver-memory)三条本来就都对,driver-sql 是唯一的例外。

修法

1. 结构性三值归约,而不是观察式判空

新增 reduceFilterNode / reduceFilterKey,在产出任何 SQL 之前把整棵 filter 树归约成 'true' / 'false' / 'clause'。这一点是刻意的:原缺陷本身就是一次「编译完再看分组里有没有东西」的观察,而观察分不清「因为本来就是空」和「因为有东西没编译出来」。结构判定没有这个盲区。

副产品:编译器打开的每一个分组都保证至少收到一条子句 —— TRUE 组合子整个跳过,$and 里的 TRUE 成员与 $or 里的 FALSE 成员按各自单位元丢弃,归约为 FALSE 的节点根本走不到发射循环。knex 再没有机会静默丢弃一个组。

FALSE 常量用 1 = 0:read-scope-sql.ts 对空 $in 用的就是它,knex 自己渲染空 whereIn 也是它,三个方言通吃,无 binding,且查询仍是普通 SELECT(LIMIT/ORDER BY/聚合都照常)。

2. 形状拒收 —— 与单位元不可分的一半

套用单位元的前提是「编译成空」只剩一个成因。在此之前 $or: [null]$or: ['x']$or: [[…]]$or: [new Date()] 同样会无痕消失;不先拦掉它们就上单位元,会把它们从「被静默忽略」升级成「匹配所有行」,比原 bug 更坏(cloud#1073 踩过的那一脚)。

现在 $and/$or 的元素与 $not 的操作数必须是 plain object 的 filter 节点,否则按 ADR-0112 响亮拒收 —— INVALID_FILTER / 400、消息指明出错位置(filter.$or[1])、不带 [sql-driver] 前缀,与本 driver 既有的 unsupportedFilterError 同一个信封。$and: 'x' 这类非数组操作数也不再被当成一个名为 $and 的字段列。

原型判定是关键的一半:Date / RegExp / class 实例都满足 typeof x === 'object' && !Array.isArray(x) 却枚举为空 —— 若被接受,归约会把它们读成 TRUE 并交出整张表。测试里有一条 class 实例专门钉这个。

刻意不做的事

验证

修前(新 pin 文件跑在未改动的 main 上)——bug 逐条复现:

× empty $or is FALSE — ZERO rows, not the whole table
× empty $not is FALSE — NOT TRUE ≡ FALSE, so zero rows
AssertionError: expected [ '1', '2', '3' ] to deeply equal []
AssertionError: expected [ '1', '2', '3' ] to deeply equal []
      Tests  2 failed | 1 passed | 31 skipped (34)

整个 pin 文件修前 22 failed | 12 passed (34);其中 { $or: [null] } 修前 resolve 出全部 3 行(「垃圾静默匹配所有行」的实证)。

修后:

packages/plugins/driver-sql  →  Test Files 58 passed | 4 skipped (62)
                                Tests    717 passed | 44 skipped (761)
                                tsc --noEmit → TYPECHECK OK

SqliteWasmDriver extends SqlDriver,继承同一个方法,一并跑过:

packages/plugins/driver-sqlite-wasm  →  Test Files 16 passed (16)
                                        Tests    232 passed (232)

(含它自己的 sqlite-wasm-filter-logic-conformance.test.ts。)

sql-driver-or-filter.test.ts(#3774 的一致性 sweep)与 sql-driver-filter-no-silent-drop.test.ts(#3948 的响亮拒收)全绿 —— 本次改动与它们钉住的行为不冲突。

用户可见性

changeset 已加(patch),并在正文里明确写出这是查询行为变更:{ $or: [] } 从返回整张表变成返回零行,依赖旧行为的代码依赖的是一个 filter 旁路,RLS 影响单列说明。


🤖 Generated with Claude Code

https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7


Generated by Claude Code

applyFilterCondition 把每个组合子都编译成一个 knex 分组回调,而 knex 对
「一个子句都没加进去的分组」不产出 SQL。于是「这个组是空的」和「这个组
已被满足」编译成了同一条查询。丢弃子句不等于套用单位元,而两个单位元的
方向相反:空 $and 是 TRUE、空 $or 是 FALSE、$not 空子分组是 FALSE。旧代码
对三者一律给全表;$and 恰好正确只是因为「丢掉」在 AND 侧碰巧等价于 TRUE。

放松的两格是安全相关的:$or: [] 最常见的来源正是「本该有条件、但循环一个
析取项都没填进去」的 RLS read scope,当成全表意味着本该看不到任何行的人
拿到了整表。formula 的 matchesFilterCondition 与 driver-memory 三条本来
就都对,driver-sql 是唯一的例外。

配套的形状拒收是同一处修复不可分的一半:套用单位元的前提是「编译成空」
只剩一个成因。$or: [null] / ['x'] / [[…]] / [new Date()] 以前同样无痕消失,
不先拦掉就上单位元会把它们从「被静默忽略」升级成「匹配所有行」,比原 bug
更坏。$and/$or 的元素与 $not 的操作数现在必须是 plain object 的 filter
节点,否则按 ADR-0112 响亮拒收(INVALID_FILTER / 400,报错指明位置)。
原型判定是关键的一半:Date/RegExp/class 实例都满足 typeof === 'object'
却枚举为空,被接受就会被读成 TRUE。

判定是结构性的(编译前先把整棵树归约成 true/false/clause 三值),而不是
「编译完再问 knex 有没有产出」—— 原缺陷本身就是后者那种观察,而观察分不清
「因为本来就是空」和「因为有东西没编译出来」。结构判定没有这个盲区,并且
保证编译器打开的每个分组都至少收到一条子句。

非空的 $and/$or/$not 编译方式完全未变。{ field: {} } 刻意不裁决(归约把带
字段键的节点一律判为 clause),该分叉另记 #5240;一致性表扩条另记 #5239。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pbu27iNUfQCHeuS551Rqo7
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 4, 2026 12:09pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Aug 4, 2026
@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/driver-sql.

9 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/data-modeling/drivers.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/anatomy.mdx (via @objectstack/driver-sql)
  • content/docs/plugins/packages.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/driver-sql)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/driver-sql)
  • content/docs/releases/implementation-status.mdx (via @objectstack/driver-sql)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 12:10
@os-zhuang
os-zhuang enabled auto-merge August 4, 2026 12:10
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 193cd5c Aug 4, 2026
24 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-5134-sql-driver-boolean-identity branch August 4, 2026 12:23
This was referenced Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

1 participant